package de.tum.in.msspp;

import java.util.ArrayList;
import java.util.List;

import de.tum.in.msspp.data.FaceRegion;
import de.tum.in.msspp.dialog.ClassifyDialog;
import de.tum.in.msspp.dialog.UserDialog;
import de.tum.in.msspp.graph.Edge;
import de.tum.in.msspp.graph.Graph;
import de.tum.in.msspp.graph.GraphException;
import de.tum.in.msspp.graph.Node;
import de.tum.in.msspp.services.ServiceProvider;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.Display;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class PictureEditor extends Activity implements View.OnClickListener {
	
	//Pictureeditor Mode
	public enum Mode 
	{
		DETECTION, RECOGNITION
	}
	
	public static final String dummyPhotographer = "Dummy";

	//Picture chooser type
	private static final int SELECT_PICTURE = 2;
	private static final int TAKE_PICTURE = 1;

	//Photographer
	String photographerName = dummyPhotographer;
	
	//Current picture with regions
	Bitmap picture;
	List<FaceRegion> regions;

	// Drawing
	Panel panel;
	float scaleFactor = 1;
	int destX = 0;
	int destY = 0;

	// Current PictureEditMode
	Mode mode = Mode.DETECTION;

	// Service & Graph
	ServiceProvider service;
	Graph graph;

	// Subdialogs
	ClassifyDialog dialog;
	UserDialog userDialog;

	@Override
	public void onCreate(Bundle savedInstanceState) 
	{
		super.onCreate(savedInstanceState);

		// GUI
		this.panel = new Panel(this);
		setContentView(panel);

		// Init service
		service = new ServiceProvider(null, this.getApplicationContext());
		
		//Try to show last opened Picture
		try 
		{
			//TODO not always works with filesystem
			if(service.lastViewedPicture()!=null)
				this.picture =  MediaStore.Images.Media.getBitmap(getContentResolver(), Uri.parse(service.lastViewedPicture()));
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
			Toast.makeText(getApplicationContext(), "Just choose or make your image", Toast.LENGTH_LONG).show();
		}
		
		//Dummy picture
		if(this.picture == null)
			this.picture =  BitmapFactory.decodeResource(getResources(), R.drawable.crullers_dummy);
		
		//Prefer last used photographer
		if(service.getLastUser() != "")
			this.photographerName = service.getLastUser();
		
		//Preferred Graph or Dummy Graph
		graph = new Graph(photographerName + "_graph");
		graph.load(getApplicationContext());
		
		//Show logindialog
		presentUserDialog();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) 
	{
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.mainmenu, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) 
	{
		switch (item.getItemId()) 
		{
		case R.id.menu_save:
			this.saveClassification();
			break;
		case R.id.menu_detect:
			this.detectFaces();
			break;
		case R.id.menu_classify:
			this.classifyFaces();
			break;
		case R.id.menu_picture:
			//Take new picture
			Intent intentTP = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
			startActivityForResult(intentTP, TAKE_PICTURE);
			break;
		case R.id.menu_select:
			//select picture
			Intent intentSP = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
			startActivityForResult(intentSP, SELECT_PICTURE);
			break;
		case R.id.menu_graph:
			//Show graphical Graph
			Intent intentGV = new Intent(this, GraphView.class);
			intentGV.putExtra("user", this.photographerName);
			startActivity(intentGV);
			break;
		default:break;
		}
		
		return true;
	}

	/**
	 * Present the login dialog to choose the photographer
	 */
	private void presentUserDialog()
	{
		userDialog = new UserDialog(this,photographerName);
	   	userDialog.setTitle("Photographer?");
	
		userDialog.setContentView(R.layout.user);
		
		EditText editView = (EditText)userDialog.findViewById(R.id.user);
		
		editView.setText(photographerName);
		Button ok = (Button)userDialog.findViewById(R.id.set_user);;
		ok.setOnClickListener(this);
		
		userDialog.show();
	}
	
	/**
	 * Presents a classification dialog for the given region
	 * @param region to classify
	 */
	private void presentClassifyDialog(FaceRegion region) 
	{	
		dialog = new ClassifyDialog(this, region);

		Node person = null;

		//If region already classified
		if (region.isPerson())
			person = this.graph.nodeWithId(region.personId);

		//Set title
		if (person != null)
			dialog.setTitle("Classified as " + person.getId());
		else
			dialog.setTitle("Classify this person");

		dialog.setContentView(R.layout.classify);
		
		EditText editView = (EditText) dialog.findViewById(R.id.name);

		//Set text
		if (person != null)
			editView.setText(person.getName());

		//Show buttons
		Button ok = (Button) dialog.findViewById(R.id.okbutton);
		Button cancel = (Button) dialog.findViewById(R.id.cancelbutton);
		Button train = (Button) dialog.findViewById(R.id.trainbutton);

		//Show train button, if its a person
		if (region.isPerson())
			train.setVisibility(Button.VISIBLE);

		//Set listener
		ok.setOnClickListener(this);
		cancel.setOnClickListener(this);
		train.setOnClickListener(this);

		dialog.show();
	}

	@Override
	public void onClick(View v) 
	{
		//classify dialog
		switch (v.getId()) 
		{
		case R.id.okbutton:
			this.classificationDone();
			break;
		case R.id.cancelbutton:
			this.classificationCanceled();
			break;
		case R.id.trainbutton:
			this.classificationShouldBeTrained();
			break;
		default:
			break;
		}
		
		//User dialog
		switch (v.getId()) 
		{
		case R.id.set_user:
			this.setUser();
			break;
		default:
			break;
		}
	}

	/**
	 * Entered a name of a person for the faceregion
	 */
	private void classificationDone() 
	{
		//Get entered name
		EditText editView = (EditText) dialog.findViewById(R.id.name);
		String name = editView.getText().toString();
		
		try 
		{
			if (dialog != null) 
			{
				//Get region
				FaceRegion region = dialog.getFaceRegion();
				
				//Try to find person
				Node node = graph.nodeWithName(name);

				//create new person/node if required
				if (node == null) 
				{
					// New Node
					Node newNode = new Node(graph.nextNodeId(), name, "new",null);
					graph.addNode(newNode);
					node = newNode;
				}

				// Set Thumbnail of the person
				if (node.getThumbnail() == null) 
				{
					Bitmap thumb = Bitmap.createBitmap(this.picture, region.x,
							region.y, region.width, region.height);
					node.setThumbnail(thumb);
				}

				//set regions personId
				region.personId = node.getId();
			}
		} 
		catch (GraphException e) 
		{
			e.printStackTrace();
		}

		//close dialog
		dialog.dismiss();
		panel.redraw();
	}

	/**
	 * Cancel classification
	 */
	private void classificationCanceled() 
	{
		if (dialog != null)
			dialog.cancel();
	}

	/**
	 * Train the service
	 */
	private void classificationShouldBeTrained() 
	{
		//Get Face
		FaceRegion region = dialog.getFaceRegion();
		Bitmap face = Bitmap.createBitmap(this.picture, region.x, region.y,
				region.width, region.height);
		
		//Train service with face
		this.service.trainFace(region.personId, face);
		
		dialog.dismiss();
		panel.redraw();
	}
	
	/**
	 * Save classification and update graph
	 */
	private void saveClassification() 
	{
		try 
		{
			//Get photographer node
			Node photographer = graph.nodeWithName(photographerName);

			//Create photographer node if required
			if (photographer == null) 
			{
				photographer = new Node(graph.nextNodeId(), photographerName, service.getId(), null);
				graph.addNode(photographer);
			}

			if (regions != null) 
			{
				//Update graph
				for (FaceRegion region : regions) 
				{
					//if region was classified
					if (region.isPerson()) 
					{
						//Find person A
						Node personA = graph.nodeWithId(region.personId);

						// Update connection between photographer and persons on picture
						Edge edge1 = graph.edgeBetweenNodes(photographer,personA);
						
						//update weight
						edge1.setWeight(edge1.getWeight() + 1);

						// Update connection between all persons on picture
						for (FaceRegion region2 : regions) 
						{
							//if region 2 was classified
							if (region2.isPerson()) 
							{
								//find person B
								Node personB = graph.nodeWithId(region2.personId);

								//No reflexive edge
								if(personA != personB)
								{
									//Find existing edge or create new on
									Edge edge2 = graph.edgeBetweenNodes(personB,personA);

									//update weight
									edge2.setWeight(edge2.getWeight() + 1);
								}
							}
						}
					}
				}
			}
			this.graph.save(getApplicationContext());

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Sets the photographer
	 */
    private void setUser()
    {
		EditText editView = (EditText)userDialog.findViewById(R.id.user);
		userDialog.setUser(editView.getText().toString());
		
		//set user...
		this.photographerName = userDialog.getUser();
		service.setLastUser(photographerName);
		
		String graphname = photographerName + "_graph";
		//Open graph of the photographer
		if(!graph.getName().equals(graphname))
		{
			graph = new Graph(graphname);
			graph.load(getApplicationContext());
		}
		
		//dismiss dialog
		userDialog.dismiss();
		panel.redraw();
	}

	
    /**
     * Detect faces at the current picture
     */
	private void detectFaces() 
	{
		mode = Mode.DETECTION;

		if(this.picture == null)
			return;
		
		this.regions = service.detectFaces(this.picture);
		panel.redraw();
	}

	/**
	 * Classify the faces at the current picture
	 */
	private void classifyFaces() {
		mode = Mode.RECOGNITION;

		if(this.picture ==null)
			return;
		
		List<FaceRegion> classifiedRegions = service.classifyDetectedFaces(this.picture);

		if (this.regions != null) 
		{
			ArrayList<FaceRegion> newRegions = new ArrayList<FaceRegion>();

			//Only classifiy faces that are marked as faces by the user
			for (FaceRegion fr : this.regions) 
			{
				//If marked as face
				if (fr.state == FaceRegion.State.FACE) 
				{
					//Check autoclassification by server
					for (FaceRegion nfr : classifiedRegions) 
					{
						//Same region = Same face
						if (fr.x == nfr.x && fr.y == nfr.y) 
						{
							//Add classified regions
							newRegions.add(nfr);
							break;
						}
					}
				}
			}

			//update regions
			this.regions = newRegions;
		} else {
			//take all classified regions...
			this.regions = classifiedRegions;
		}

		panel.redraw();
	}

	@Override
	
	public void onResume(){
		super.onResume();
		graph.load(getApplicationContext());
		
	}
	
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) 
	{
		super.onActivityResult(requestCode, resultCode, data);

		String pictureURL = null;

		//Took a new picture
		if (resultCode == RESULT_OK && requestCode == TAKE_PICTURE) 
		{	
			//Make timestamp
			String timestamp = Long.toString(System.currentTimeMillis());
			Bitmap image = (Bitmap) data.getExtras().get("data");
		
			//Set image
			this.picture = image;
			this.regions = null;
			
			//Mode is detection
			this.mode = Mode.DETECTION;
			
			//get url of the picture
			pictureURL = MediaStore.Images.Media.insertImage(
					getContentResolver(), this.picture, timestamp, timestamp);
		}

		//Selected an existing picture
		if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) 
		{
			//Get url of the picture
			Uri selectedImage = data.getData();
			pictureURL = selectedImage.getPath();

			try 
			{
				//Set image
				this.picture = resizeToScreen(MediaStore.Images.Media.getBitmap(getContentResolver(), selectedImage));
				this.regions = null;
			} 
			catch (Exception e) 
			{
				e.printStackTrace();
				Toast.makeText(getApplicationContext(), "Couldn't open Image",
						Toast.LENGTH_SHORT);
			}
		}

		// Tracks the last used picture
		if (pictureURL != null)
			this.service.setLastViewPicture(pictureURL);

	}

	
	/**
	 * Resizes the given image to screen bounds
	 * @param bmp - image to resize
	 * @return resized image
	 */
	
	public Bitmap resizeToScreen(Bitmap bmp) 
	{
		int bitWidth = bmp.getWidth();
		int bitHeight = bmp.getHeight();
		
		int destWidth;
		int destHeight;
		
		Display display = getWindowManager().getDefaultDisplay();
		int displayWidth = display.getWidth();
		int displayHeight = display.getHeight();

		float xRatio = (float) displayWidth / (float) bitWidth;
		float yRatio = (float) displayHeight / (float) bitHeight;

		//Scale image
		if (xRatio < yRatio) {
			destWidth = (int) (bitWidth * xRatio);
			destHeight = (int) (bitHeight * xRatio);
			scaleFactor = xRatio;
			return Bitmap.createScaledBitmap(bmp, destWidth, destHeight, true);
		} else {
			destWidth = (int) (bitWidth * yRatio);
			destHeight = (int) (bitHeight * yRatio);
			scaleFactor = yRatio;
			return Bitmap.createScaledBitmap(bmp, destWidth, destHeight, true);
		}

	}

	/* Inner Class to draw the image */

	class Panel extends SurfaceView implements SurfaceHolder.Callback 
	{
		public Panel(Context context) 
		{
			super(context);
			this.getHolder().addCallback(this);
			this.setFocusable(true);
		}

		/**
		 * Retunrs the correct color for the given region
		 * @param fr - face region
		 * @return color of the faceregion
		 */
		private int colorForRegion(FaceRegion fr) 
		{
			//For the correct mode
			if (mode == Mode.DETECTION)
				switch (fr.state) 
				{
				case FACE:
					return Color.GREEN;
				case NOFACE:
					return Color.RED;
				default:
					return Color.GRAY;
				}
			else if (fr.isPerson())
				return Color.WHITE;
			else
				return Color.BLACK;
		}

		/**
		 * Redraw panel
		 */
		public void redraw() 
		{
			Canvas c = getHolder().lockCanvas();
			this.onDraw(c);
			getHolder().unlockCanvasAndPost(c);
		}

		@Override
		public void onDraw(Canvas canvas) 
		{
			Paint paint = new Paint();
			Paint textPaint = new Paint();
			textPaint.setColor(Color.RED);
			textPaint.setTextSize(20);

			canvas.drawColor(Color.BLACK);

			float xFactor = 1;
			float yFactor = 1;
			int destWidth;
			int destHeight;

			// draw picture
			if (picture != null) 
			{
				//draw scaled bitmap
				xFactor = ((float) canvas.getWidth())
						/ ((float) picture.getWidth());
				yFactor = ((float) canvas.getHeight())
						/ ((float) picture.getHeight());

				if (xFactor < yFactor) {
					destWidth = (int) (picture.getWidth() * xFactor);
					destHeight = (int) (picture.getHeight() * xFactor);
					scaleFactor = xFactor;
				} else {
					destWidth = (int) (picture.getWidth() * yFactor);
					destHeight = (int) (picture.getHeight() * yFactor);
					scaleFactor = yFactor;
				}

				// center image
				destX = (canvas.getWidth() - destWidth) / 2;
				//destY = (canvas.getHeight() - destHeight) / 2;

				canvas.drawBitmap(Bitmap.createScaledBitmap(picture, destWidth,
						destHeight, true), destX, destY, null);
			}

			// draw regions
			if (regions != null) 
			{
				for (FaceRegion fr : regions) 
				{
					paint.setColor(colorForRegion(fr));
					paint.setAlpha(100);
					paint.setStyle(Paint.Style.STROKE);
					paint.setStrokeWidth(3);

					canvas.drawRect((fr.x * scaleFactor) + destX,
							(fr.y * scaleFactor) + destY,
							((fr.x + fr.width) * scaleFactor) + destX,
							((fr.y + fr.height) * scaleFactor) + destY, paint);
					
					Node person = graph.nodeWithId(fr.personId);
					if(person != null)
						canvas.drawText(person.getName(), (fr.x * scaleFactor) + destX, (fr.y * scaleFactor) + destY, textPaint);
				}
			}
		}

		@Override
		public boolean onTouchEvent(MotionEvent event) 
		{
			if (event.getAction() == MotionEvent.ACTION_DOWN) 
			{
				if (regions != null) 
				{
					for (FaceRegion fr : regions) 
					{
						int x = (int) event.getX();
						int y = (int) event.getY();

						if ((fr.x * scaleFactor) + destX <= x
								&& (fr.x + fr.width) * scaleFactor + destX >= x
								&& (fr.y * scaleFactor) + destY <= y
								&& (fr.y + fr.height) * scaleFactor + destY >= y) 
						{
							//Touch in current faceregion
							
							if (mode == Mode.DETECTION) 
							{
								// Touch in DetectionMode
								switch (fr.state) 
								{
								case FACE:
									fr.state = FaceRegion.State.NOFACE;
									break;
								case NOFACE:
									fr.state = FaceRegion.State.FACE;
									break;
								default:
									break;
								}
							} 
							else 
							{
								//Show classify dialog
								presentClassifyDialog(fr);
							}
						}
					}
				}

				this.redraw();
				return true;
			}

			return false;
		}

		@Override
		public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
			this.redraw();
		}

		@Override
		public void surfaceCreated(SurfaceHolder holder) {
			this.redraw();
		}

		@Override
		public void surfaceDestroyed(SurfaceHolder holder) {
			//Nothing to do
		}
	}
}
